﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Windows.Forms;
using MostRecentlyUsed.Interfaces;
using MostRecentlyUsed.Models.EventArguments;
using Newtonsoft.Json;

namespace MostRecentlyUsed
{
    /// <summary>
    /// Menu item for managing most recently used database connections
    /// </summary>
    public partial class DatabaseConnections : ToolStripMenuItem, IDatabaseConnections
    {
        private List<SelectedConnectionString> _connectionStrings = new List<SelectedConnectionString>();

        /// <summary>
        /// Initializes a new instance of the <see cref="DatabaseConnections"/> class
        /// </summary>
        public DatabaseConnections()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Object for handling encrytion/decryption of the connection strings for persistent secure storage
        /// </summary>
        [Browsable(false)]
        public IEncryption EncryptionClass { get; set; }

        /// <summary>
        /// Password Hash
        /// </summary>
        [Category("Security")]
        public string PasswordHash { get; set; }

        /// <summary>
        /// Salt key
        /// </summary>
        [Category("Security")]
        public string SaltKey { get; set; }

        /// <summary>
        /// Maximum number of items to display in the Most Recent list
        /// </summary>
        public int MaximumNumberOfItems { get; set; } = 4;

        /// <summary>
        /// RegEx expressions for sensitive data that must be replaced for the Display Name
        /// </summary>
        public string[] ItemsToReplace { get; set; } = new string[] { "Password=.*;", "Password=.*$", "pwd=.*;", "pwd=.*$" };

        /// <summary>
        /// Occurs when a menu item is clicked on
        /// </summary>
        public event ItemSelectedEventHandler ItemSelectedEvent;

        /// <summary>
        /// Unencrypted list of connection string items
        /// </summary>
        [ReadOnly(true)]
        public List<SelectedConnectionString> ConnectionStrings
        {
            get
            {
                return _connectionStrings;
            }
        }

        /// <summary>
        /// Gets the connection string items as an encrypted string for secure persistent storage
        /// </summary>
        /// <returns>Encrypted string</returns>
        public string GetEncryptedConnectionStrings()
        {
            var connectionStrings = JsonConvert.SerializeObject(_connectionStrings, Formatting.None);

            InitialiseEncryption();

            return EncryptionClass.Encrypt(connectionStrings);
        }

        /// <summary>
        /// Decrypts the value into the connection strings items
        /// </summary>
        /// <param name="value">Encrypted connection strings</param>
        public void SetEncryptedConnectionStrings(string value)
        {
            InitialiseEncryption();

            var connectionStrings = JsonConvert.DeserializeObject<List<SelectedConnectionString>>(EncryptionClass.Decrypt(value));

            DropDownItems.Clear();

            foreach (var item in connectionStrings.OrderByDescending(o => o.Order))
            {
                AddItem(item);
            }
        }


        /// <summary>
        /// Adds a new item to the connection string list; it the item already exists, it is promoted to the top of the list.  Items beyond the <see cref="MaximumNumberOfItems"/> are removed
        /// </summary>
        /// <param name="connectionString">Connection string to store</param>
        /// <param name="provider">Database provider</param>
        public void AddItem(string connectionString, string provider)
        {
            AddItem(new SelectedConnectionString(connectionString, provider, ItemsToReplace));
        }

        /// <summary>
        /// /// Adds a new item to the connection string list; it the item already exists, it is promoted to the top of the list.  Items beyond the <see cref="MaximumNumberOfItems"/> are removed
        /// </summary>
        /// <param name="connectionStringItem">Connection String object</param>
        public void AddItem(SelectedConnectionString connectionStringItem)
        {
            connectionStringItem.Order = 0;

            if (Contains(connectionStringItem.ConnectionString))
            {
                if (DropDownItems.IndexOfKey(connectionStringItem.DisplayName) >= 0)
                {
                    DropDownItems.RemoveAt(DropDownItems.IndexOfKey(connectionStringItem.DisplayName));
                }

                RemoveItemByDisplayName(connectionStringItem.DisplayName);
            }

            var i = 1;

            foreach (var item in _connectionStrings.OrderBy(o => o.Order))
            {
                item.Order = i;
                i++;

                if (item.Order >= MaximumNumberOfItems)
                {
                    _connectionStrings.Remove(item);
                }
            }

            _connectionStrings.Add(connectionStringItem);

            var newItem = new ConnectionStringMenuItem(connectionStringItem);
            newItem.Name = connectionStringItem.DisplayName;
            newItem.Click += OnNewItemClick;

            DropDownItems.Insert(0, newItem);
        }

        /// <summary>
        /// Check to see whether a given connection string is already in the list
        /// </summary>
        /// <param name="connectionString">Connection string</param>
        /// <returns>True if connection string already exists</returns>
        public virtual bool Contains(string connectionString)
        {
            return _connectionStrings.Count(c => c.ConnectionString == connectionString) > 0;
        }

        /// <summary>
        /// Removes a connection string by using it's display name as the key
        /// </summary>
        /// <param name="displayName">Display name</param>
        public virtual void RemoveItemByDisplayName(string displayName)
        {
            var connectionStringString = _connectionStrings.Where(c => c.DisplayName == displayName).FirstOrDefault();
            _connectionStrings.Remove(connectionStringString);
        }

        /// <summary>
        /// Raises the <see cref="ItemSelectedEvent"/> event.
        /// </summary>
        /// <param name="sender">Object raising the event</param>
        /// <param name="e">An <see cref="System.EventArgs"/> that contains the event data.</param>
        public void OnNewItemClick(object sender, EventArgs e)
        {
            if (ItemSelectedEvent != null && sender as ConnectionStringMenuItem != null)
            {
                var item = (ConnectionStringMenuItem)sender;
                AddItem(item.ConnectionString);
                ItemSelectedEvent(this, item.ConnectionString);
            }
        }

        private void InitialiseEncryption()
        {
            if (EncryptionClass == null)
            {
                var encryption = new Encryption();

                encryption.PasswordHash = string.IsNullOrWhiteSpace(PasswordHash) ? encryption.PasswordHash : PasswordHash;
                encryption.SaltKey = string.IsNullOrWhiteSpace(SaltKey) ? encryption.SaltKey : SaltKey;
                EncryptionClass = encryption;
            }
        }
    }
}
